/*
 * Copyright (C) 2010,2011.
 * AHCP Project
 * All rights reserved.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at 
 *
 *     http://www.apache.org/licenses/LICENSE-2.0 
 *
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an "AS IS"
 * BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either 
 * express or implied. See the License for the specific language
 * governing permissions and limitations under the License.
 */
package org.jacp.demo.service;

import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;

import javax.imageio.ImageIO;

import org.im4java.core.ConvertCmd;
import org.im4java.core.IM4JavaException;
import org.im4java.core.IMOperation;
import org.im4java.process.ProcessExecutor;
import org.jacp.api.action.IAction;
import org.jacp.api.action.IActionListener;
import org.jacp.demo.io.ThumbnailExtractor;
import org.jacp.demo.main.ImageDemoLauncher;
import org.jacp.demo.repository.ImageInfoCacheRepository;
import org.jacp.demo.util.ActionType;
import org.jacp.demo.util.ImageCacheAction;
import org.jacp.demo.util.ImageFileFilter;
import org.jacp.demo.util.ProccessTerminationListener;
import org.jacp.demo.util.RecursiveCacheAction;
import org.jacp.swing.rcp.component.AStatelessComponent;

/**
 * creates image cache for files and directories
 * 
 * @author Andy Moncsek
 * 
 */
public class ImageCacheService extends AStatelessComponent {

	private volatile ImageInfoCacheRepository cacheRepository;
	private static ProccessTerminationListener listener = null;
	private static ProcessExecutor exec = null;
	private final ThumbnailExtractor extractor = new ThumbnailExtractor();

	public ImageCacheService() {

	}

	static {
		final Runtime runtime = Runtime.getRuntime();
		final int nrOfProcessors = runtime.availableProcessors();
		// do not overload system !!
		exec = new ProcessExecutor(nrOfProcessors>1?(nrOfProcessors-1):nrOfProcessors);

	}
	private IMOperation op = null;

	@Override
	public Object handleAction(final IAction<ActionEvent, Object> action) {
		final Object message = action.getLastMessage();
		if (message instanceof ImageCacheAction) {
			final ImageCacheAction myAction = (ImageCacheAction) message;
			final String path = myAction.getPath();
			final ActionType type = myAction.getType();
			@SuppressWarnings("unchecked")
			final List<String> panels = (List<String>) myAction.getResult();
			createCache(path, panels, type);

		} else if (message instanceof RecursiveCacheAction) {
			final RecursiveCacheAction myAction =(RecursiveCacheAction)message;
			final String path = myAction.getPath();
			final ActionType type = myAction.getType();
			@SuppressWarnings("unchecked")
			final List<String> panels = (List<String>) myAction.getResult();
			final File currentCacheDir = getTargetCacheFolder(path);
			handleCacheFiles(path, currentCacheDir, panels, type);
			
		}
		return null;
	}

	private void createCache(final String path, final List<String> panels,
			final ActionType mode) {
		final File currentCacheDir = getTargetCacheFolder(path);
		if (currentCacheDir.exists()) {
			// cache already created
			final File[] cacheDirFiles = currentCacheDir
					.listFiles(new ImageFileFilter());
			final int sizeCache = cacheDirFiles != null ? cacheDirFiles.length
					: 0;
			final File[] origFileDir = new File(path)
					.listFiles(new ImageFileFilter());
			final int sourceSize = origFileDir != null ? origFileDir.length : 0;
			if (sourceSize > sizeCache) {
				// check files, if cache entry does not exists
			}
		} else {
			if (currentCacheDir.mkdir()) {				
				handleCacheFiles(path, currentCacheDir, panels, mode);
			}
		}
	}
	
	private final void handleCacheFiles(final String path,final File currentCacheDir,final List<String> panels,final ActionType mode){
		// create thumbnails
		final List<List<String>> devide = splitList(panels, 10);
		if(devide.size()>1) {	
			for(int i=0;i<devide.size()-1;i++) {
				// send recursive calls
				sendMessageToService(new RecursiveCacheAction(path, mode, devide.get(i)));
			}
			// take last entry (list) and convert in current thread
			createThumnails(currentCacheDir.getAbsolutePath() + "/",
					devide.get(devide.size()-1), mode);
		} else {
			createThumnails(currentCacheDir.getAbsolutePath() + "/",
					panels, mode);
		}
	}
	
	private void sendMessageToService(final RecursiveCacheAction action) {
		final IActionListener<ActionListener, ActionEvent, Object> listener = getActionListener();
		listener.getAction().setMessage(action);
		listener.getListener().actionPerformed(
				listener.getAction().getActionEvent());
	}
	
	private final File getTargetCacheFolder(final String path){
		// check if current path has already thumbnail cache
		final File cacheDir = ImageDemoLauncher.getCacheDir();
		// TODO only for unix!!
		final String pathCacheDir = path.replace("/", "");
		final File currentCacheDir = new File(cacheDir.getAbsolutePath() + "/"
				+ pathCacheDir + "/");		
		return currentCacheDir;
		
	}
	
	
	 private <A>List<List<A>> splitList(List<A> list,
			    int maxListSize) {
			final List<List<A>> splittedList = new ArrayList<List<A>>();
			int itemsRemaining = list.size();
			int start = 0;
			while (itemsRemaining != 0) {
			    int end = itemsRemaining >= maxListSize ? (start + maxListSize)
				    : (start + itemsRemaining);
			    splittedList.add(list.subList(start, end));

			    int sizeOfFinalList = end - start;
			    itemsRemaining = itemsRemaining - sizeOfFinalList;
			    start = start + sizeOfFinalList;
			}

			return splittedList;
		    }


	/**
	 * create a single cache entry
	 * 
	 * @param targetFolder
	 * @param image
	 */
	private void createCacheEntry(final String targetFolder, final String image) {
		final String name = targetFolder
				+ image.substring(image.lastIndexOf("/") + 1,
						image.lastIndexOf('.'));

		getCacheRepository().insertCacheRecord(image,
				name + "_small.jpg:" + name + "_med.jpg");
	}

	private void saveCacheFile(final BufferedImage image,
			final String destFielName) {
		// Encode as a JPEG
		try {
			ImageIO.write(image, "jpg", new File(destFielName));
		} catch (IOException e1) {
			e1.printStackTrace();
		}
	}

	private void createThumnails(final String targetFolder,
			final List<String> images, final ActionType mode) {
		final IMOperation op = getImageOperation();

		for (int i = 0; i < images.size(); i++) {
			final String fileName = images.get(i);
			final String destination = getCorrectTargetFileName(fileName,
					targetFolder);
			final BufferedImage tmp = extractor
					.getThumbnail(new File(fileName));

			if (tmp != null) {
				// thumbnail was extracted from exif, svae it
				saveCacheFile(tmp, destination);

			} else {
				try {
					// convert image to thumbnail
					final ConvertCmd cmd = getCMD(mode);
					exec.execute(cmd.getProcessTask(op, fileName, destination));

				} catch (final IOException e) {
					e.printStackTrace();
				} catch (final IM4JavaException e) {
					e.printStackTrace();
				}
			}
			createCacheEntry(targetFolder, fileName);
		}

	}

	private final String getCorrectTargetFileName(final String fileName,
			final String targetFolder) {
		final int lastDot = fileName.lastIndexOf('.');
		final String dstImageTmp = fileName.substring(0, lastDot)
				+ "_small.jpg";
		final String dstImage = dstImageTmp.substring(
				dstImageTmp.lastIndexOf("/") + 1, dstImageTmp.length());
		final String destination = targetFolder + dstImage;
		return destination;
	}

	/**
	 * get image magic console
	 * 
	 * @return
	 */
	private final ConvertCmd getCMD(final ActionType mode) {
		final ConvertCmd cmd = new ConvertCmd();
		if (mode.equals(ActionType.PARALLEL)) {
			cmd.setAsyncMode(true);
		}
		cmd.addProcessEventListener(getListener());
		return cmd;
	}

	private synchronized IMOperation getImageOperation() {
		if (op == null) {
			op = new IMOperation();
			op.size(160);
			op.addImage();
			op.thumbnail(160);
			op.addImage();

		}
		return op;
	}

	public ImageInfoCacheRepository getCacheRepository() {
		return cacheRepository;
	}

	public void setCacheRepository(
			final ImageInfoCacheRepository cacheRepository) {
		this.cacheRepository = cacheRepository;
	}

	public synchronized ProccessTerminationListener getListener() {
		if (listener == null) {
			listener = new ProccessTerminationListener(getActionListener());
		}
		return listener;
	}

}
